// Copyright 2016 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT

#include "object/futex_context.h"

#include <assert.h>
#include <lib/ktrace.h>
#include <trace.h>
#include <zircon/types.h>

#include <kernel/sched.h>
#include <kernel/thread_lock.h>
#include <object/process_dispatcher.h>
#include <object/thread_dispatcher.h>

#define LOCAL_TRACE 0

namespace {  // file scope only

// By default, Futex KTracing is disabled as it introduces some overhead in user
// mode operations which might be performance sensitive.  Developers who are
// debugging issues which could involve futex interactions may enable the
// tracing by setting this top level flag to true, provided that their
// investigation can tolerate the overhead.
constexpr bool kEnableFutexKTracing = false;

class KTraceBase {
 public:
  enum class FutexActive { Yes, No };
  enum class RequeueOp { Yes, No };

 protected:
  static constexpr uint32_t kCountSaturate = 0xFE;
  static constexpr uint32_t kUnlimitedCount = 0xFFFFFFFF;
};

template <bool Enabled>
class KTrace;

template <>
class KTrace<false> : public KTraceBase {
 public:
  KTrace() {}
  void FutexWait(uintptr_t futex_id, thread_t* new_owner) {}
  void FutexWoke(uintptr_t futex_id, zx_status_t result) {}
  void FutexWake(uintptr_t futex_id, FutexActive active, RequeueOp requeue_op, uint32_t count,
                 thread_t* assigned_owner) {}
  void FutexRequeue(uintptr_t futex_id, FutexActive active, uint32_t count,
                    thread_t* assigned_owner) {}
};

template <>
class KTrace<true> : public KTraceBase {
 public:
  KTrace() : ts_(ktrace_timestamp()) {}

  void FutexWait(uintptr_t futex_id, thread_t* new_owner) {
    ktrace(TAG_FUTEX_WAIT, static_cast<uint32_t>(futex_id), static_cast<uint32_t>(futex_id >> 32),
           static_cast<uint32_t>(new_owner ? new_owner->user_tid : 0),
           static_cast<uint32_t>(arch_curr_cpu_num() & 0xFF), ts_);
  }

  void FutexWoke(uintptr_t futex_id, zx_status_t result) {
    ktrace(TAG_FUTEX_WOKE, static_cast<uint32_t>(futex_id), static_cast<uint32_t>(futex_id >> 32),
           static_cast<uint32_t>(result), static_cast<uint32_t>(arch_curr_cpu_num() & 0xFF), ts_);
  }

  void FutexWake(uintptr_t futex_id, FutexActive active, RequeueOp requeue_op, uint32_t count,
                 thread_t* assigned_owner) {
    if ((count >= kCountSaturate) && (count != kUnlimitedCount)) {
      count = kCountSaturate;
    }

    uint32_t flags = (arch_curr_cpu_num() & KTRACE_FLAGS_FUTEX_CPUID_MASK) |
                     ((count & KTRACE_FLAGS_FUTEX_COUNT_MASK) << KTRACE_FLAGS_FUTEX_COUNT_SHIFT) |
                     ((requeue_op == RequeueOp::Yes) ? KTRACE_FLAGS_FUTEX_WAS_REQUEUE_FLAG : 0) |
                     ((active == FutexActive::Yes) ? KTRACE_FLAGS_FUTEX_WAS_ACTIVE_FLAG : 0);

    ktrace(TAG_FUTEX_WAKE, static_cast<uint32_t>(futex_id), static_cast<uint32_t>(futex_id >> 32),
           static_cast<uint32_t>(assigned_owner ? assigned_owner->user_tid : 0), flags, ts_);
  }

  void FutexRequeue(uintptr_t futex_id, FutexActive active, uint32_t count,
                    thread_t* assigned_owner) {
    if ((count >= kCountSaturate) && (count != kUnlimitedCount)) {
      count = kCountSaturate;
    }

    uint32_t flags = (arch_curr_cpu_num() & KTRACE_FLAGS_FUTEX_CPUID_MASK) |
                     ((count & KTRACE_FLAGS_FUTEX_COUNT_MASK) << KTRACE_FLAGS_FUTEX_COUNT_SHIFT) |
                     KTRACE_FLAGS_FUTEX_WAS_REQUEUE_FLAG |
                     ((active == FutexActive::Yes) ? KTRACE_FLAGS_FUTEX_WAS_ACTIVE_FLAG : 0);

    ktrace(TAG_FUTEX_WAKE, static_cast<uint32_t>(futex_id), static_cast<uint32_t>(futex_id >> 32),
           static_cast<uint32_t>(assigned_owner ? assigned_owner->user_tid : 0), flags, ts_);
  }

 private:
  const uint64_t ts_;
};

using KTracer = KTrace<kEnableFutexKTracing>;

inline zx_status_t ValidateFutexPointer(user_in_ptr<const zx_futex_t> value_ptr) {
  if (!value_ptr || (reinterpret_cast<uintptr_t>(value_ptr.get()) % sizeof(zx_futex_t))) {
    return ZX_ERR_INVALID_ARGS;
  }
  return ZX_OK;
}

// Get a reference to the thread that the user is asserting is the new owner of the futex.
// The thread must belong to the same process as the caller as futexes may not be owned
// by threads from another process.
inline zx_status_t ValidateNewFutexOwner(zx_handle_t new_owner_handle,
                                         fbl::RefPtr<ThreadDispatcher>* new_owner_thread_out) {
  DEBUG_ASSERT(new_owner_thread_out != nullptr);
  DEBUG_ASSERT(*new_owner_thread_out == nullptr);

  if (new_owner_handle == ZX_HANDLE_INVALID) {
    return ZX_OK;
  }

  auto up = ProcessDispatcher::GetCurrent();
  zx_status_t status = up->GetDispatcherWithRights(new_owner_handle, 0, new_owner_thread_out);
  if (status != ZX_OK) {
    return status;
  }

  if ((*new_owner_thread_out)->process() != up) {
    new_owner_thread_out->reset();
    return ZX_ERR_INVALID_ARGS;
  }

  return ZX_OK;
}

}  // namespace

template <OwnedWaitQueue::Hook::Action action>
OwnedWaitQueue::Hook::Action FutexContext::SetBlockingFutexId(thread_t* thrd, void* ctx) {
  // Any thread involved in one of these operations is
  // currently blocked on a futex's wait queue, and therefor
  // *must* be a user mode thread.
  DEBUG_ASSERT((thrd != nullptr) && (thrd->user_thread != nullptr));
  thrd->user_thread->blocking_futex_id_ = reinterpret_cast<uintptr_t>(ctx);
  return action;
}

FutexContext::FutexState::~FutexState() {}

FutexContext::FutexContext() { LTRACE_ENTRY; }

FutexContext::~FutexContext() {
  LTRACE_ENTRY;

  // All of the threads should have removed themselves from wait queues and
  // destroyed themselves by the time the process has exited.
  DEBUG_ASSERT(futex_table_.is_empty());
  DEBUG_ASSERT(free_futexes_.is_empty());
}

zx_status_t FutexContext::GrowFutexStatePool() {
  fbl::AllocChecker ac;
  ktl::unique_ptr<FutexState> new_state{new (&ac) FutexState()};

  if (!ac.check()) {
    return ZX_ERR_NO_MEMORY;
  }

  Guard<fbl::Mutex> guard{&lock_};
  free_futexes_.push_front(ktl::move(new_state));
  return ZX_OK;
}

void FutexContext::ShrinkFutexStatePool() {
  ktl::unique_ptr<FutexState> state;
  {  // Do not let the futex state become released inside of the lock.
    Guard<fbl::Mutex> guard{&lock_};
    DEBUG_ASSERT(free_futexes_.is_empty() == false);
    state = free_futexes_.pop_front();
  }
}

// FutexWait verifies that the integer pointed to by |value_ptr| still equals
// |current_value|. If the test fails, FutexWait returns FAILED_PRECONDITION.
// Otherwise it will block the current thread until the |deadline| passes, or
// until the thread is woken by a FutexWake or FutexRequeue operation on the
// same |value_ptr| futex.
zx_status_t FutexContext::FutexWait(user_in_ptr<const zx_futex_t> value_ptr,
                                    zx_futex_t current_value, zx_handle_t new_futex_owner,
                                    const Deadline& deadline) {
  LTRACE_ENTRY;
  zx_status_t result;

  // Make sure the futex pointer is following the basic rules.
  result = ValidateFutexPointer(value_ptr);
  if (result != ZX_OK) {
    return result;
  }

  auto current_thread = ThreadDispatcher::GetCurrent();
  uintptr_t futex_id = reinterpret_cast<uintptr_t>(value_ptr.get());
  {
    // FutexWait() checks that the address value_ptr still contains
    // current_value, and if so it sleeps awaiting a FutexWake() on value_ptr.
    // Those two steps must together be atomic with respect to FutexWake().
    // If a FutexWake() operation could occur between them, a userland mutex
    // operation built on top of futexes would have a race condition that
    // could miss wakeups.
    Guard<fbl::Mutex> guard{&lock_};

    // Sanity check, bookkeeping should not indicate that we are blocked on
    // a futex at this point in time.
    DEBUG_ASSERT(current_thread->blocking_futex_id_ == 0);

    int value;
    result = value_ptr.copy_from_user(&value);
    if (result != ZX_OK) {
      return result;
    }
    if (value != current_value) {
      return ZX_ERR_BAD_STATE;
    }

    // Now that we have validated the futex state, validate the proposed new owner.
    fbl::RefPtr<ThreadDispatcher> futex_owner_thread;
    result = ValidateNewFutexOwner(new_futex_owner, &futex_owner_thread);

    if (result != ZX_OK) {
      return result;
    }

    // When attempting to wait, the new owner of the futex (if any) may not be
    // the thread which is attempting to wait.
    if (futex_owner_thread.get() == ThreadDispatcher::GetCurrent()) {
      return ZX_ERR_INVALID_ARGS;
    }

    // Find the FutexState for this futex.  If there is no FutexState
    // already, then there are no current waiters.  Grab a free futex futex
    // struct from the pool, and add it to the hash table instead.
    //
    // Either way, make sure that we hold a reference to the FutexState that
    // we end up with.  We will want to keep it alive in order to optimize
    // the case where we are removed from the wait queue for a reason other
    // then being explicitly woken.  If we fail to do this, it is possible
    // for us to time out on the futex, then have someone else return the
    // futex to the free pool, and finally have the futex removed from the
    // free pool and destroyed by an exiting thread.
    FutexState* futex = ObtainActiveFutex(futex_id);
    if (futex == nullptr) {
      futex = ActivateFromPool(futex_id);
    } else {
      // If there was already a FutexState (implying that there are
      // currently waiters, and perhaps an owner) verify that the thread
      // we are attempting to make the new futex owner (if any) is not
      // already waiting on the target futex.
      if (futex_owner_thread) {
        if (futex_owner_thread->blocking_futex_id_ == futex_id) {
          return ZX_ERR_INVALID_ARGS;
        }
      }
    }

    // Record the futex ID of the thread we are about to block on.
    current_thread->blocking_futex_id_ = futex_id;

    // Enter the thread lock (exchanging the futex context lock for the
    // thread spin-lock in the process) and wait on the futex wait queue,
    // assigning ownership properly in the process.
    //
    // We specifically want reschedule=MutexPolicy::NoReschedule here,
    // otherwise the combination of releasing the mutex and enqueuing the
    // current thread would not be atomic, which would mean that we could
    // miss wakeups.
    Guard<spin_lock_t, IrqSave> thread_lock_guard{ThreadLock::Get()};
    ThreadDispatcher::AutoBlocked by(ThreadDispatcher::Blocked::FUTEX);
    guard.Release(MutexPolicy::ThreadLockHeld, MutexPolicy::NoReschedule);

    thread_t* new_owner = futex_owner_thread ? &futex_owner_thread->thread_ : nullptr;

    KTracer tracer;
    tracer.FutexWait(futex_id, new_owner);

    current_thread->thread_.interruptable = true;
    result = futex->waiters_.BlockAndAssignOwner(deadline, new_owner, ResourceOwnership::Normal);
    current_thread->thread_.interruptable = false;
  }

  // If we were woken by another thread, then our block result will be ZX_OK.
  // We know that the thread who woke us up will have returned the FutexState
  // to the free list if needed.  No special action should be needed by us at
  // this point.
  //
  // If the result is not ZX_OK, then additional actions may be required by
  // us.  This could be because
  //
  // 1) We hit the deadline (ZX_ERR_TIMED_OUT)
  // 2) We were killed (ZX_ERR_INTERNAL_INTR_KILLED)
  // 3) We were suspended (ZX_ERR_INTERNAL_INTR_RETRY)
  //
  // In any one of these situations, it is possible that we were the last
  // waiter in our FutexState and need to return the FutexState to the free
  // pool as a result.  To complicate things just a bit further, becuse of
  // zx_futex_requeue, the futex that we went to sleep on may not be the futex
  // we just woke up from.  We need to re-enter the context's futex lock and
  // revalidate the state of the world.
  KTracer tracer;
  if (result == ZX_OK) {
    // The FutexWake operation should have already cleared our blocking
    // futex ID.
    DEBUG_ASSERT(current_thread->blocking_futex_id_ == 0);
    tracer.FutexWoke(futex_id, result);
    return ZX_OK;
  }

  {
    Guard<fbl::Mutex> guard{&lock_};

    DEBUG_ASSERT(current_thread->blocking_futex_id_ != 0);
    FutexState* futex = ObtainActiveFutex(current_thread->blocking_futex_id_);
    tracer.FutexWoke(current_thread->blocking_futex_id_, result);
    current_thread->blocking_futex_id_ = 0;

    // Important Note:
    //
    // It is possible for this thread to have exited via an error path
    // (timeout, killed, whatever), but for our futex context to have
    // already been returned to the free pool.  It is not possible, however,
    // for the blocking_futex_id to ever be 0 at this point.  The sequence
    // which would produce something like this is as follows.
    //
    // 1) Threads A is blocked in a Futex X's wait queue.
    // 2) Thread A times out, and under the protection of the ThreadLock is
    //    removed from the wait queue by the kernel.  The wait queue now has
    //    no waiters, but futex X's FutexState has not been returned to the
    //    pool yet.
    // 3) Before thread A makes it to the guard at the top of this block,
    //    Thread B comes along and attempts to wake at least one thread from
    //    futex X.
    // 4) Thread B is inside of the processes futex context lock when it
    //    does this, it notices that futex X's wait queue is now empty, so it
    //    returns the queue to the free pool.
    // 5) Finally, thread A makes it into the futex context lock and
    //    discovers that it had been waiting on futex X, but futex X is not in
    //    the set of active futexes.
    //
    // There are many other variations on this sequence, this just happens
    // to be the simplest one that I can think of.  Other threads can be
    // involved, futex X could have been retired, then reactivated any
    // number of times, and so on.
    //
    // The important things to realize here are...
    // 1) An truly active futex *must* have at least one waiter.
    // 2) Because of timeouts, it is possible for a futex to be in the
    //    active set, with no waiters.
    // 3) If #2 is true, then there must be at least one thread which has
    //    been released from the wait queue and it traveling along the error
    //    path.
    // 4) One of these threads will make it to here, enter the lock, and
    //    attempt to retire the FutexState to the inactive pool if it is
    //    still in the active set.
    // 5) It does not matter *who* does this, as long as someone does this
    //    job.  It can be the waking thread, or one of the timed out
    //    threads.  As long as everyone makes an attempt while inside of the
    //    lock, things should be OK and no FutexStates should be leaked.
    if (futex != nullptr) {
      // Looks like the futex is still in the active set.  Enter the
      // thread_lock and check to see if the OwnedWaitQueue member of this
      // FutexState is now empty.  If so, then we need to release the wait
      // queue owner, update any related PI pressure, and return the futex
      // state to the available pool.
      bool is_empty = false;
      {
        Guard<spin_lock_t, IrqSave> thread_lock_guard{ThreadLock::Get()};
        DEBUG_ASSERT(current_thread->thread_.blocking_wait_queue == nullptr);
        is_empty = futex->waiters_.IsEmpty();
        if (is_empty) {
          if (futex->waiters_.AssignOwner(nullptr)) {
            sched_reschedule();
          }
        }
      }

      if (is_empty) {
        ReturnToPool(futex);
      }
    }
  }

  return result;
}

zx_status_t FutexContext::FutexWake(user_in_ptr<const zx_futex_t> value_ptr, uint32_t wake_count,
                                    OwnerAction owner_action) {
  LTRACE_ENTRY;
  zx_status_t result;
  KTracer tracer;

  // Make sure the futex pointer is following the basic rules.
  result = ValidateFutexPointer(value_ptr);
  if (result != ZX_OK) {
    return result;
  }

  uintptr_t futex_id = reinterpret_cast<uintptr_t>(value_ptr.get());
  AutoReschedDisable resched_disable;  // Must come before the Guard.
  {                                    // explicit lock scope for clarity.
    Guard<fbl::Mutex> guard{&lock_};

    // If the futex key is not in our hash table, then there is no one to
    // wake, we are finished.
    FutexState* futex = ObtainActiveFutex(futex_id);
    if (futex == nullptr) {
      tracer.FutexWake(futex_id, KTracer::FutexActive::No, KTracer::RequeueOp::No, wake_count,
                       nullptr);
      return ZX_OK;
    }

    // Now, enter the thread lock and actually wake up the threads.
    // OwnedWakeQueue will handle the ownership bookkeeping for us.
    bool futex_emptied;
    {
      resched_disable.Disable();
      Guard<spin_lock_t, IrqSave> thread_lock_guard{ThreadLock::Get()};

      using Action = OwnedWaitQueue::Hook::Action;
      auto hook = (owner_action == OwnerAction::RELEASE)
                      ? SetBlockingFutexId<Action::SelectAndKeepGoing>
                      : SetBlockingFutexId<Action::SelectAndAssignOwner>;

      if (futex->waiters_.WakeThreads(wake_count, {hook, nullptr})) {
        sched_reschedule();
      }

      futex_emptied = futex->waiters_.IsEmpty();
      tracer.FutexWake(futex_id, KTracer::FutexActive::Yes, KTracer::RequeueOp::No, wake_count,
                       futex->waiters_.owner());
    }

    // Now that we are outside of the thread lock, if there are no longer
    // any waiters for this futex, return the state to the pool.
    if (futex_emptied) {
      ReturnToPool(futex);
    }
  }

  return ZX_OK;
}

zx_status_t FutexContext::FutexRequeue(user_in_ptr<const zx_futex_t> wake_ptr, uint32_t wake_count,
                                       int current_value, OwnerAction owner_action,
                                       user_in_ptr<const zx_futex_t> requeue_ptr,
                                       uint32_t requeue_count,
                                       zx_handle_t new_requeue_owner_handle) {
  LTRACE_ENTRY;
  zx_status_t result;
  KTracer tracer;

  // Make sure the futex pointers are following the basic rules.
  result = ValidateFutexPointer(wake_ptr);
  if (result != ZX_OK) {
    return result;
  }

  result = ValidateFutexPointer(requeue_ptr);
  if (result != ZX_OK) {
    return result;
  }

  if (wake_ptr.get() == requeue_ptr.get()) {
    return ZX_ERR_INVALID_ARGS;
  }

  AutoReschedDisable resched_disable;  // Must come before the Guard.
  Guard<fbl::Mutex> guard{&lock_};

  int value;
  result = wake_ptr.copy_from_user(&value);
  if (result != ZX_OK) {
    return result;
  }

  if (value != current_value) {
    return ZX_ERR_BAD_STATE;
  }

  // Now that we have validated the futex state, validate the proposed new owner.
  fbl::RefPtr<ThreadDispatcher> requeue_owner_thread;
  result = ValidateNewFutexOwner(new_requeue_owner_handle, &requeue_owner_thread);
  if (result != ZX_OK) {
    return result;
  }

  // Find the FutexState for the wake and requeue futexes.
  uintptr_t wake_id = reinterpret_cast<uintptr_t>(wake_ptr.get());
  uintptr_t requeue_id = reinterpret_cast<uintptr_t>(requeue_ptr.get());
  FutexState* wake_futex = ObtainActiveFutex(wake_id);
  FutexState* requeue_futex = ObtainActiveFutex(requeue_id);

  // Verify that the thread we are attempting to make the requeue target's
  // owner (if any) is not waiting on either the wake futex or the requeue
  // futex.
  if (requeue_owner_thread && ((requeue_owner_thread->blocking_futex_id_ == wake_id) ||
                               (requeue_owner_thread->blocking_futex_id_ == requeue_id))) {
    return ZX_ERR_INVALID_ARGS;
  }

  thread_t* new_requeue_owner = requeue_owner_thread ? &(requeue_owner_thread->thread_) : nullptr;
  KTracer::FutexActive requeue_futex_was_active =
      (requeue_futex == nullptr) ? KTracer::FutexActive::No : KTracer::FutexActive::Yes;

  // If we have no waiters for the wake futex, then we are more or less
  // finished.  Just be sure to re-assign the futex owner for the requeue
  // futex if needed.
  if (wake_futex == nullptr) {
    tracer.FutexWake(wake_id, KTracer::FutexActive::No, KTracer::RequeueOp::Yes, wake_count,
                     nullptr);
    tracer.FutexRequeue(requeue_id, requeue_futex_was_active, requeue_count, new_requeue_owner);

    if (requeue_futex != nullptr) {
      Guard<spin_lock_t, IrqSave> thread_lock_guard{ThreadLock::Get()};

      thread_t* new_owner =
          (requeue_owner_thread != nullptr) ? &requeue_owner_thread->thread_ : nullptr;

      if (requeue_futex->waiters_.AssignOwner(new_owner)) {
        sched_reschedule();
      }
    }
    return ZX_OK;
  }

  // If we plan to make an attempt to requeue _any_ threads, make sure that we
  // have a requeue target ready.
  if (requeue_count && (requeue_futex == nullptr)) {
    requeue_futex = ActivateFromPool(requeue_id);
  }

  // Now that all of our sanity checks are complete, it is time to do the
  // actual manipulation of the various wait queues.  Start by disabling
  // rescheduling and entering the thread lock.
  resched_disable.Disable();
  bool wake_futex_emptied;
  bool requeue_futex_emptied;
  {
    DEBUG_ASSERT(wake_futex != nullptr);
    Guard<spin_lock_t, IrqSave> thread_lock_guard{ThreadLock::Get()};
    bool do_resched;

    using Action = OwnedWaitQueue::Hook::Action;
    auto wake_hook = (owner_action == OwnerAction::RELEASE)
                         ? SetBlockingFutexId<Action::SelectAndKeepGoing>
                         : SetBlockingFutexId<Action::SelectAndAssignOwner>;
    auto requeue_hook = SetBlockingFutexId<Action::SelectAndKeepGoing>;

    if (requeue_count) {
      DEBUG_ASSERT(requeue_futex != nullptr);
      do_resched = wake_futex->waiters_.WakeAndRequeue(
          wake_count, &(requeue_futex->waiters_), requeue_count, new_requeue_owner,
          {wake_hook, nullptr}, {requeue_hook, reinterpret_cast<void*>(requeue_id)});
    } else {
      do_resched = wake_futex->waiters_.WakeThreads(wake_count, {wake_hook, nullptr});
    }

    tracer.FutexWake(wake_id, KTracer::FutexActive::Yes, KTracer::RequeueOp::Yes, wake_count,
                     wake_futex->waiters_.owner());
    tracer.FutexRequeue(requeue_id, requeue_futex_was_active, requeue_count, new_requeue_owner);

    wake_futex_emptied = wake_futex->waiters_.IsEmpty();
    requeue_futex_emptied = (requeue_futex != nullptr) && requeue_futex->waiters_.IsEmpty();

    if (do_resched) {
      sched_reschedule();
    }
  }

  // Make sure we have retuned any now-empty futex states to the pool before
  // requesting a reschedule (if needed).
  if (wake_futex_emptied) {
    ReturnToPool(wake_futex);
  }

  if (requeue_futex_emptied) {
    ReturnToPool(requeue_futex);
  }

  return ZX_OK;
}

// Get the KOID of the current owner of the specified futex, if any, or ZX_KOID_INVALID if there
// is no known owner.
zx_status_t FutexContext::FutexGetOwner(user_in_ptr<const zx_futex_t> value_ptr,
                                        user_out_ptr<zx_koid_t> koid_out) {
  zx_status_t result;

  // Make sure the futex pointer is following the basic rules.
  result = ValidateFutexPointer(value_ptr);
  if (result != ZX_OK) {
    return result;
  }

  zx_koid_t koid = ZX_KOID_INVALID;
  uintptr_t futex_id = reinterpret_cast<uintptr_t>(value_ptr.get());
  {
    Guard<fbl::Mutex> guard{&lock_};
    FutexState* futex = ObtainActiveFutex(futex_id);
    if (futex != nullptr) {
      Guard<spin_lock_t, IrqSave> thread_lock_guard{ThreadLock::Get()};

      if (const thread_t* owner = futex->waiters_.owner(); owner != nullptr) {
        // Any thread which owns a FutexState's wait queue *must* be a
        // user mode thread.
        DEBUG_ASSERT(owner->user_thread != nullptr);
        koid = owner->user_thread->get_koid();
      }
    }
  }

  return koid_out.copy_to_user(koid);
}
